# -*- coding: utf-8 -*-
#########################################################################
## This scaffolding model makes your app work on Google App Engine too
#########################################################################
from datetime import datetime

if request.env.web2py_runtime_gae:            

    #~ db = DAL('gae')                           
    #~ session.connect(request, response, db=db)

	#MEMCACHE
	# http://web2py.com/book/default/chapter/11#Memcache
    from gluon.contrib.gae_memcache import MemcacheClient
    from gluon.contrib.memdb import MEMDB
    cache.memcache = MemcacheClient(request)
    cache.ram = cache.disk = cache.memcache

    db = DAL('gae')
    session.connect(request,response,MEMDB(cache.memcache))
    
else:                                         
    db = DAL('sqlite://storage.sqlite')       


#########################################################################
## Here is sample code if you need:
## - email capabilities
## - authentication (registration, login, logout, ... )
## - authorization (role based authorization)
## - crud actions
## uncomment as needed
#########################################################################

#########################################################################
## 
##                 AUTH & Mail
## 
#########################################################################

from gluon.tools import *
#from gluon.tools import Mail, Auth, Crud     # new in web2py 1.56
mail=Mail()                                  # mailer
mail.settings.server='smtp.gmail.com:587'    # your SMTP server
mail.settings.sender='laisvosprogramos@gmail.com'         # your email
mail.settings.login='laisvosprogramos:laisv4programoms'      # your credentials



auth=Auth(globals(),db)                # authentication/authorization

auth.settings.hmac_key='<a your secret key>'

auth.settings.mailer=mail                    # for user email verification
auth.define_tables()                         # creates all needed tables
auth.messages.verify_email =  'Click on the link http://127.0.0.1/problem/default/user/verify_email/%(key)s to verify your email'


#########################################################################
# Add needed roles (if not exist)
#~ auth.add_group('examples_editor', 'Can edit (create/update/delete) Examples')
#~ auth.add_group('topics_editor', 'Can edit (create/update/delete) Topics')



#########################################################################
##     def data(): return dict(form=crud())
##
##     def download(): return response.download(request,db)  # in controller default.py
#########################################################################

crud=Crud(globals(),db)                      # for CRUD helpers using auth
# crud.settings.auth=auth                      # (optional) enforces authorization on crud

service=Service(globals())             # for json, xml, jsonrpc, xmlrpc, amfrpc


########################################################################
## uncomment the following line if you do not want sessions
#session.forget()
#########################################################################

"""
Table definition
"""
db.define_table("Locales",
      Field('locale', 'string', ), 
      Field('name', 'string',),
)
if db(db.Locales.id>0).count() == 0:
    #~ db.Locales.bulk_insert(
        #~ [ dict(locale=locale, name=name) for (locale, name) in
                #~ {'lt-lt': u"Lietuvių", 'en-en': u"English"}.items()
        #~ ]
    #~ )
    for (locale, name) in {'lt-lt': u"Lietuvių", 'en-en': u"English"}.items():
        db.Locales.insert( locale=locale, name=name )
        mylog( "inserted locale:", locale, name  )

"""
Table definition
"""
db.define_table("Versioning",
    # http://web2py.com/book/default/chapter/07#Record-Versioning
      Field('author_id', db.auth_user, label=T("Author"),  default=auth.user_id, update=auth.user_id, writable=False,
            requires=IS_EMPTY_OR(IS_IN_DB(db, 'auth_user.id','auth_user.first_name')),
            #~ represent = lambda id: "%(first_name)s (%(id)s)" % dict( [(field, db.auth_user[id][field]) for field in 'first_name id'.split()] ),
            represent = lambda id: ( "%(first_name)s (%(id)s)" % db.auth_user[id] ) if db.auth_user[id] else id
      ), #, readable=False
      Field('timestamp', 'datetime', default=request.now, update=request.now, writable=False),
      Field('version_id', 'string', default=request.now), # kind of unique id, by which one could trach stuff in archives -- an easy way to allways access it via url
      Field('comment', 'string'),  # short comment on what is changed
      Field('changed_fields', 'list:string', default=[],
            #~ update=['explanation'],
            #~ requires=IS_EMPTY_OR(IS_IN_SET(['explanation', 'output'], multiple=True))
            requires=IS_IN_SET(['explanation', 'output'], multiple=True) # for Examples it should be output_tpl -- TODO: make naming more consistent -- get rid of _tpl in favour of plain (and plain rename to _cached)
           ),  # a list of changed fields should have requires=IS_IN_SET..

      )

"""
Table definition
"""
# could be also versioned -- but let's leave it for future :)
db.define_table("Topics_without_unique_and_parent",
      
      Field("name_translations", "text"), # used for topics translations -- name is left untouched in English
                                          # as "name" should be used for precode templates inclusion...
                                          # translations should be made using @@lang:xx@@ bla bla @@lang:end@@ or similary..
      Field("explanation", "text"), #can be appended/overriden? in Example
      #~ Field("input_data", "text"),             # sometimes examples would be based on some data (but this could probably fit in explanation field)
      Field("output", "text"),             #can be overriden in Example  # probably will be deprecated
      #~ Field("output_tpl", "text"),      #can be overriden in Example  # probably will be deprecated
      Field("order_in_topic", "integer"),        #order of topic in parent topic (if many)
       # auxilary -- to help analyse db or logs -- and take care of dependancies in code_templates
      Field('topic_path', "string", writable=False),   # auxilary -- to help analyse db or logs
                                                  # maybe should be http://www.web2py.com/book/default/chapter/06#Computed-Fields
      db.Versioning,
      )

db.define_table("Topics",
          Field("parent_id","reference Topics", label=T("Parent Topic"), 
                widget=my_hierarchical_options_widget,
                requires=IS_EMPTY_OR(IS_IN_DB(db, 'Topics.id','Topics.name'))
                #~ requires=IS_EMPTY_OR(IS_IN_DB(db, 'Topics.id','%(id)s'))
          ),
          Field('topic_path_ids', "list:reference Topics", default=[], writable=False,  #default='||',
                #~ requires=IS_EMPTY_OR(IS_IN_DB(db, 'Topics.id', 'Topics.name', multiple=True))
                requires=IS_IN_DB(db, 'Topics.id', 'Topics.name', multiple=True)
          ),
          Field("name", "string", length=70, unique=True), # unique is required for template @@include feature
                                                           # but example shouldn't be unique, author also should be taken into account
                                                           # !!! maybe "topic_path" should better be unique
                                                           # as the same name might be meaningfull in different branches
          db.Topics_without_unique_and_parent
      )



ROOT_FLAG_4PARENT_ID = None  # TODO: might be better 0

def is_topic_root(topic):
    #~ return topic.parent_id == root_id
    #~ return topic['parent_id'] == None
    return (topic['parent_id'] == ROOT_FLAG_4PARENT_ID) or (topic['parent_id'] == topic['id'])

def is_topic_root_by_id(id, Topics_data=db.Topics):
    #~ return topic.parent_id == root_id
    return is_topic_root(Topics_parents_data[id])

# update root topics to become none # adapted for GAE - no JOins
# root_ids_for_update_parent_id = [row.id for row in  db().select(db.Topics.ALL).find(is_topic_root) ]
# db(db.Topics.id in root_ids_for_update_parent_id ).update(parent_id=None)
for topic in db().select(db.Topics.ALL).find( lambda row: row.id==row.parent_id) :
    topic.update_record( parent_id = ROOT_FLAG_4PARENT_ID )

def find_root_id(): # TODO: pay attention, that there could be several roots (parent_id=None) someday 
    root_topic = db(db.Topics.name == 'root').select().first()
    if root_topic:
        #~ if root_topic.parent_id != root_id:
            #~ raise Exception('root_topic.parent_id != root_id (%s)' % root_id)
        #~ if root_topic.parent_id != None:
        if not is_topic_root(root_topic):
            #~ pass
            root_topic.update_record(parent_id=None)
            msg = 'root_topic.parent_id was not None (id: %d, name: %s) -- forced parent_id=None ;)' % (root_topic.id, root_topic.name)
            response.flash = msg
            raise Exception(msg )
            
    else: # try to find in more general way
        root_topic = db(db.Topics.parent_id == ROOT_FLAG_4PARENT_ID).select().first() # here we take only first one!!

    if root_topic: # if we found anything 
        root_id = root_topic.id
        mylog( dict(root_id=root_id) )
        return root_id

def find_root_topics():
    #~ return db((db.Topics.parent_id == None) || ( db.Topics.id == )).select() 
    return  db().select(db.Topics.ALL).find( is_topic_root )

        
# some helper to find root topic id
# and to set default topic parent_id...
root_id = find_root_id()
if root_id:
    db.Topics.parent_id.default = int(root_id)


db.define_table('Topics_archive', 
            Field('current_record',db.Topics, 
                requires=IS_IN_DB(db, 'Topics.id', 'Topics.name') # maybe path?
            ),
            Field("parent_id",db.Topics, label=T("Parent Topic"), 
                widget=my_hierarchical_options_widget, requires=IS_EMPTY_OR(IS_IN_DB(db, 'Topics.id','Topics.name'))
            ),
          #~ Field('topic_path_ids', "list:reference Topics", default=[], # writable=False,  #default='||',
                #~ requires=IS_EMPTY_OR((IS_IN_DB(db, 'Topics.id', 'Topics.name', multiple=True)))
                #~ )
            Field("name", "string", length=70), # unique is required for template @@include feature
            db.Topics_without_unique_and_parent,
       )

#~ # db.Topics.name.unique=True  # would be easier, but is not allowed
db.define_table("myTable",
          Field("name", "string", length=70, unique=True), 
          db.Versioning
          )
        
#~ db.define_table('myTable_archive', 
            #~ Field('current_record',db.myTable, 
                #~ requires=IS_IN_DB(db, 'myTable.id', 'myTable.name') # maybe path?
                #~ ),
          #~ Field("comment", "string"), 
          #~ Field("name", "string", length=70, unique=True), 
          #~ db.Versioning
          #~ )                

    
"""
Table definition
"""
db.define_table("Languages",
      Field("name", "string", unique=True),
      Field("about", "text"),
      Field("extension", "string", unique=True),   # or could be extensions??
      Field("comment_till_line_end", "string", ),  # for explanations insertion when downloading example as file
      Field("comment_brackets", "string", ),       # separated by one space, like "/* */"
      )


"""
Table definition
"""
db.define_table("Metadata",
    
      #~ Field('rating', "integer",  writable=False), # probably better to use plugin
      
      
      )

"""
Table definition
"""


"""
Table for pre/post-code
"""
db.define_table("CodeSurroundings", 
      #etc
      # various stuff which is needed for full functioning of the example code
      # can inherit or override these fields from other examples (accessible by topic tree)
      # should (must?) only include previous topics (parents of current topic 
      # or parrallel topics with lower ID or lower ordering)
      # we can include any of parts: pre_code/code/post_code
      # usage:
      # @@include example_topic_name
      # @@include example_topic_name[.pre_code|code|post_code]
      # by default it should include the same part as current 
      # by default it would include parent topic example
      # @@include 
      # @@include .pre_code
      # or the same:
      # @@include __parent__
      # @@include __parent__.pre_code
      # branched inclusion (kind of multiple inheritance is probably not implemented)

      # there might be confusions when using personalized examples (if their names differ from public)
      # so maybe
      # @@include example_topic_name#id
      # or
      # @@include example_topic_name*author
      #

      
      #
      # TODO: rename *_tpl --> *_src    and * --> *_cache
      # this would be more clear what are the field for
      # now for example: pre_code_tpl is used to define the contents
      # and pre_code only stores/caches the generated values (by parse_code_templates)
      #
      # reminder: if we would  generate those cachable values on save (not on view),
      # then we MIGHT miss some updates to further level includable content :/ 
      
      
      Field("pre_code_tpl", "text", default="@@include __parent__"),   
      Field("pre_code", "text"),  
      Field("pre_code_timestamp", "datetime"),
      Field("pre_code_4parsing_dbg_info", "text", writable=False, represent=lambda txt:  XML(txt)   ),  

      Field("post_code_tpl", "text", default="@@include __parent__"),
      Field("post_code", "text"),
      Field("post_code_timestamp", "datetime"),
      Field("post_code_4parsing_dbg_info", "text", writable=False,  represent=lambda txt:  XML(txt)  ),

    )
"""
Table definition
"""

db.define_table("QuestionsTests",
      # testing possibility (human tests, not unittests ;)
      Field("expected_input", "string"), # for 'test' feature
      Field("expected_answer", "string"), # for 'test' feature
      Field("example_type", "string", default='repeat', requires=IS_IN_SET(set('repeat test'.split())) ), #, requires=IS_IN_SET('repeat', 'test')), # repeat | test

)

"""
Table definition
"""
db.define_table("ExamplesKernel", 
      #for uniquity check might be possible otions: "update" and "compute"
      Field("topic_id", db.Topics, label="Topic",  
            widget=my_hierarchical_options_widget ,     
            requires=IS_IN_DB(db, 'Topics.id','Topics.name')
            ),
      Field("lang_id", db.Languages,label="Language", 
                      requires=IS_IN_DB(db, 'Languages.id','Languages.name')),  

      Field("language_can_do_this", "boolean",  default=True), # this can be unchecked if the language doesn't support the idea in topic
                                                               # feature should be updated in old examples
      
      Field("explanation", "text",  default=""), #will be appended (override??) explanation in Topic 
      Field("code", "text",  default=""),  # the essence of example :)

      Field("output", "text", default=""), # especially for non-python -- to store expected output..
      Field("output_tpl", "text", default="@@include topic.output"), # especially for non-python -- to store expected output..
      Field("output_timestamp", "datetime"),
    )

db.define_table("Examples", 

      db.ExamplesKernel,      
      db.CodeSurroundings,
      #~ db.Metadata, 
      db.QuestionsTests,
      db.Versioning,
      
      # for template rendering dependencies --
      # used by during parse_code_templates(..)
      # TODO: someday make separate table for rendered/cached code_templates -- as latter could be applied per example_code_part
      # influences not really needed, except for possible optimization -- one select less
      #~ Field("tpl_influences", "list:reference Examples", default=[], writable=False),  # ids of influenced examples // not topics
      Field("pre_code_tpl_depends_on", "list:reference Examples", default=[], writable=False),  # ids of influencing examples // not topics -- better list:reference
      Field("pre_code_tpl_valid", "boolean", default=False, ),

      Field("post_code_tpl_depends_on", "list:reference Examples", default=[], writable=False),  # ids of influencing examples // not topics -- better list:reference
      Field("post_code_tpl_valid", "boolean", default=False, ),

      Field("output_tpl_depends_on", "list:reference Examples", default=[], writable=False),  # ids of influencing examples // not topics -- better list:reference
      Field("output_tpl_valid", "boolean", default=False, ),

      # TODO TOTHINK ERRORPRONE:
      # what if "pre_code" depends on other example "code"?? @@include __parent__.code

      # should there be decart product reference fields???      
      # because now invalidation in example_update or so are made only by the same field...
      # and only parsing shows, what really depends on what ...
      # so best is to @@include only the same field
      
      Field("tpl_invalidator", "reference Examples",  ),  # these remain per example (not per field)
      Field("tpl_invalidation_timestamp", "datetime"),
      
)

db.Examples.tpl_invalidator.requires=IS_EMPTY_OR(IS_IN_DB(db, 'Examples.id', '%(id)s')),


#~ db.Examples.tpl_invalidator.requires=IS_EMPTY_OR(IS_IN_SET( [ item.id for item in db().select(db.Examples.ALL) ] ) )

#~ db.Examples.tpl_invalidator.requires=IS_IN_DB(db, 'Examples.id')

#~ db.Topics.changed_fields.update=['explanation']
#~ db.Examples.changed_fields.update=['explanation']

db.Examples.changed_fields.requires=IS_IN_SET(
                        ['explanation', 'output_tpl']+ \
                        "pre_code_tpl code post_code_tpl".split()+ \
                        db.QuestionsTests.fields,
                        multiple=True
                )

"""
Examples Archive 
"""
db.define_table('Examples_archive',
   Field('current_record',db.Examples, 
        requires=IS_IN_DB(db, 'Examples.id',) # 'Examples.topic_id.name Examples.lang_id.name' 
        ), 
   db.Examples
   )


db.define_table("Cache_keys", 
    Field('key_','string', unique=True),
    Field('url','string', ),
    Field('content','text'),
)

"""
REQUIRES:         Relations between tables (remove fields you don't need from requires)
"""

#db.Examples.example_type.requires=IS_IN_SET(set('repeat test'.split()))
#db.Examples.type_id.requires=IS_IN_DB(db, 'ExampleTypes.id','ExampleTypes.name')


"""
REPRESENTS
"""

db.Examples.lang_id.represent=lambda id:  db.Languages[id].name if id and db.Languages[id] else None


def get_topic_path_ids(id, hierarchy_info=None, include_root=True): #, table=db.Examples, tid_field='topic_id'):
    if id == None:  # if we get None 
        return []
    if hierarchy_info==None:
        hierarchy_info = prefetch_db_table_to_dict('Topics')
    tid = int(id) # otherwise it can stay str (example: '4') and cause some problems later
    tpath = []
    levels = 0; max_levels = 10; # prevention of infinite loop
    try:
        while tid:
            # if root
            #~ if tid == hierarchy_info[tid]['parent_id']: # or hierarchy_info[tid]['parent_id'] == None
            if is_topic_root(hierarchy_info[tid]): # could check it without calling is_topic_root, but this is more consistent if root identification changes
                if include_root:
                    tpath.insert(0, tid) # prepend to tpath
                break
            tpath.insert(0, tid) # prepend to tpath
            tid = hierarchy_info[tid]['parent_id']
            levels += 1
            if levels > max_levels:
                mylog(  "\n\n      Suspiciously many levels: might be infinite loop \n" )
                break
    except Exception, e:
        mylog( 'ERROR in get_topic_path_ids', msg=str(e) )
    return tpath
   
  
topic_path_sep = ' > '
#~ def get_topic_path_names_with_ordering(id):
    #~ #if not id:  return ''
    #~ tpath = get_topic_path_ids(id)
    #~ print "id, tpaht: ", id, tpath 
    #~ tpath_wnames = []
#~ 
    #~ for id in tpath:    
        #~ topic = db.Topics[id];
        #~ #print 'topics', topics
        #~ order_str = topic.order_in_topic.str() if  topic.order_in_topic else ''
        #~ tpath_wnames.append("%s_%s" % (order_str, topic.name) ) 
           #~ 
    #~ return topic_path_sep.join( tpath_wnames)

def get_topic_path_names(id,  names_info=db.Topics, topic_path_ids=None, include_root=False):
    topic_path_ids = topic_path_ids or get_topic_path_ids(id)
    if not id:
        return None
    if not topic_path_ids:
        try:
            result = names_info[id]['name']
            if not T('root').lower() in result.lower():
                result += T(' (root)')
            return result
        except TypeError, e:
            return str(id)+" (no info)"

    return topic_path_sep.join([names_info[tid].name for tid in topic_path_ids])
    
db.Topics.parent_id.represent=get_topic_path_names

#~ db.Topics.topic_path_ids.compute=lambda r: get_topic_path_ids(r.parent_id, include_root=True)

#~ db.Topics.topic_path_ids.compute=lambda r: "|%s|" % '|'.join( map(str, get_topic_path_ids(r.parent_id, include_root=True)) )

#~ #db.Topics.topic_path.compute=lambda r: get_topic_path_names(r.parent_id, topic_path_ids=r.topic_path_ids, include_root=True) # is problem when passing topic_path_ids, as they probably might be not computed then??
#~ db.Topics.topic_path.compute=lambda r: get_topic_path_names(r.parent_id, include_root=True)
db.Examples.topic_id.represent=get_topic_path_names

#db.Examples.topic_id.represent=lambda id: db.Topics[db.Topics[id].parent_id].name +' > '+ db.Topics[id].name


def examples_select( conditions ):
    """
    customized select -- automatically pays attention to preferences on authors
    Should return just one example per language for each topic. if several examples are availabe (by different authgors), should sort out, which is to be taken.
    """
    return db( conditions  ).select()  # TODO
    
    #return db( conditions && db.Examples.author_id == session.user_id ).select() # 
    
def example_has_info( id , data__Examples = db.Examples):
    """
    a way to check that example has some code (or other meaningfull info)
    """
    example = data__Examples[id]
    if example:
        for field in  'explanation code'.split(): #  db.ExamplesKernel.fields 
            if example[field]:
                return True

example_is_empty=example_has_info # alias
    
